﻿using HeUtils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;

namespace HeAuthLib
{
    public class HeAuth
    {
        private readonly RandomNumberGenerator Rand = RandomNumberGenerator.Create();
        private byte[][] cCache = new byte[128][];
        private int cCacheLoopIndex = 0; // 记录循环覆盖缓存的位置
        private byte[] secret = null;

        public HeAuth(byte[] secret)
        {
            this.secret = secret;
        }

        public HeAuth(string secretBase64)
        {
            secret = Convert.FromBase64String(secretBase64);
        }

        public byte[] Challenge()
        {
            byte[] c = new byte[16];
            bool inCache = false;
            for (int i = 0; i < cCache.Length; i++)
            {
                Rand.GetBytes(c);
                // 如果比较到了缓存的空位仍然不相同，就放入缓存，并结束比较
                if (null == cCache[i])
                {
                    cCache[i] = c;
                    inCache = true;
                    Console.WriteLine("Added Challenge into cCache[" + i + "] vacancy.");
                    break;
                }
                //Console.WriteLine("Compare cCache i=" + i);
                if (BytesCompare.MemcmpCompare(c, cCache[i]))
                {
                    Console.WriteLine("The generate challenge be same to cCache[" + i + "], regenerate...");
                    // 如果相同，重新生成，从头比较
                    i = -1;
                    continue;
                }
            }
            if (!inCache)
            {
                if (cCacheLoopIndex >= cCache.Length)
                {
                    cCacheLoopIndex = 0;
                }
                Console.WriteLine("Insert Challenge cover cCache[" + cCacheLoopIndex + "].");
                cCache[cCacheLoopIndex++] = c;
            }
            return c;
        }

        public string ChallengeBase64()
        {
            return Convert.ToBase64String(Challenge());
        }

        private bool CheckTimestamp(long serverTimestamp)
        {
            long current = Timestamp.Milliseconds();
            long max = current + 1000 * 60;
            long min = current - 1000 * 60;
            if ((serverTimestamp >= min) && (serverTimestamp <= max))
            {
                return true;
            }
            return false;
        }

        private byte[] GenerateKey(byte[] c, byte[] r, byte[] t)
        {
            byte[] metadata = new byte[secret.Length + c.Length + r.Length + t.Length];
            Array.Copy(secret, metadata, secret.Length);
            Array.Copy(c, 0, metadata, secret.Length, c.Length);
            Array.Copy(r, 0, metadata, secret.Length + c.Length, r.Length);
            Array.Copy(t, 0, metadata, secret.Length + c.Length + r.Length, t.Length);
            byte[] hash = Sha256Util.ChecksumByte(metadata);
            byte[] key = new byte[16];
            Array.Copy(hash, key, key.Length);
            return key;
        }

        public byte[] Validation(byte[] c, byte[] r, byte[] t, byte[] e)
        {
            long serverTimestamp;
            if (BitConverter.IsLittleEndian)
            {
                serverTimestamp = IPAddress.NetworkToHostOrder(BitConverter.ToInt64(t, 0));
            }
            else
            {
                serverTimestamp = BitConverter.ToInt64(t, 0);
            }
            if (!CheckTimestamp(serverTimestamp))
            {
                throw new ArgumentException("Invalid timestamp.");
            }
            byte[] key = GenerateKey(c, r, t);
            byte[] p = AES.CBCDecrypt(e, key, c);
            return Sha256Util.ChecksumByte(p);
        }
    }
}
